#include "Camera.h"
#include <random>
#include <cmath>

Camera::Camera(float screenWidth, float screenHeight)
    : x_(0.0f), y_(0.0f)
    , screenWidth_(screenWidth), screenHeight_(screenHeight)
    , hasWorldBounds_(false)
    , worldMinX_(0.0f), worldMinY_(0.0f), worldMaxX_(0.0f), worldMaxY_(0.0f)
    , target_(nullptr)
    , followMode_(CameraFollowMode::None)
    , smoothness_(5.0f)
    , targetOffsetX_(0.0f), targetOffsetY_(0.0f)
    , deadZoneEnabled_(false)
    , deadZoneWidth_(100.0f), deadZoneHeight_(100.0f)
    , shakeTimer_(0.0f), shakeIntensity_(0.0f), shakeDecay_(5.0f)
    , shakeOffsetX_(0.0f), shakeOffsetY_(0.0f)
{
}

void Camera::setPosition(float x, float y) {
    x_ = x;
    y_ = y;
    clampToBounds();
}

void Camera::translate(float deltaX, float deltaY) {
    setPosition(x_ + deltaX, y_ + deltaY);
}

void Camera::setScreenSize(float width, float height) {
    screenWidth_ = width;
    screenHeight_ = height;
    clampToBounds();
}

void Camera::setWorldBounds(float minX, float minY, float maxX, float maxY) {
    hasWorldBounds_ = true;
    worldMinX_ = minX;
    worldMinY_ = minY;
    worldMaxX_ = maxX;
    worldMaxY_ = maxY;
    clampToBounds();
}

void Camera::setTarget(GameObject* target) {
    target_ = target;
}

void Camera::setTargetOffset(float offsetX, float offsetY) {
    targetOffsetX_ = offsetX;
    targetOffsetY_ = offsetY;
}

void Camera::setDeadZone(float width, float height) {
    deadZoneWidth_ = width;
    deadZoneHeight_ = height;
}

void Camera::update(float deltaTime) {
    updateFollowTarget(deltaTime);
    updateCameraShake(deltaTime);
    clampToBounds();
}

void Camera::updateFollowTarget(float deltaTime) {
    if (!target_ || followMode_ == CameraFollowMode::None) {
        return;
    }
    
    Transform* targetTransform = target_->getTransform();
    if (!targetTransform) {
        return;
    }
    
    // Calculate desired camera position (center target on screen with offset)
    float targetX = targetTransform->getX() + targetTransform->getWidth() / 2.0f;
    float targetY = targetTransform->getY() + targetTransform->getHeight() / 2.0f;
    
    float desiredX = targetX - screenWidth_ / 2.0f + targetOffsetX_;
    float desiredY = targetY - screenHeight_ / 2.0f + targetOffsetY_;
    
    // Handle dead zone
    if (deadZoneEnabled_ && followMode_ != CameraFollowMode::Instant) {
        float deadZoneLeft = x_ + (screenWidth_ - deadZoneWidth_) / 2.0f;
        float deadZoneRight = deadZoneLeft + deadZoneWidth_;
        float deadZoneTop = y_ + (screenHeight_ - deadZoneHeight_) / 2.0f;
        float deadZoneBottom = deadZoneTop + deadZoneHeight_;
        
        // Only move camera if target is outside dead zone
        if (targetX >= deadZoneLeft && targetX <= deadZoneRight) {
            desiredX = x_; // Don't move horizontally
        } else if (targetX < deadZoneLeft) {
            desiredX = targetX - deadZoneWidth_ / 2.0f - (screenWidth_ - deadZoneWidth_) / 2.0f;
        } else {
            desiredX = targetX + deadZoneWidth_ / 2.0f - (screenWidth_ + deadZoneWidth_) / 2.0f;
        }
        
        if (targetY >= deadZoneTop && targetY <= deadZoneBottom) {
            desiredY = y_; // Don't move vertically
        } else if (targetY < deadZoneTop) {
            desiredY = targetY - deadZoneHeight_ / 2.0f - (screenHeight_ - deadZoneHeight_) / 2.0f;
        } else {
            desiredY = targetY + deadZoneHeight_ / 2.0f - (screenHeight_ + deadZoneHeight_) / 2.0f;
        }
    }
    
    // Apply follow mode
    switch (followMode_) {
        case CameraFollowMode::Instant:
            setPosition(desiredX, desiredY);
            break;
            
        case CameraFollowMode::Smooth:
        case CameraFollowMode::Delayed: {
            float lerpFactor = smoothness_ * deltaTime;
            if (lerpFactor > 1.0f) lerpFactor = 1.0f;
            
            float newX = lerp(x_, desiredX, lerpFactor);
            float newY = lerp(y_, desiredY, lerpFactor);
            setPosition(newX, newY);
            break;
        }
        
        case CameraFollowMode::None:
        default:
            break;
    }
}

void Camera::updateCameraShake(float deltaTime) {
    if (shakeTimer_ <= 0.0f) {
        shakeOffsetX_ = 0.0f;
        shakeOffsetY_ = 0.0f;
        return;
    }
    
    shakeTimer_ -= deltaTime;
    
    // Generate random shake offset
    float shakeX = random(-shakeIntensity_, shakeIntensity_);
    float shakeY = random(-shakeIntensity_, shakeIntensity_);
    
    shakeOffsetX_ = shakeX;
    shakeOffsetY_ = shakeY;
    
    // Decay shake intensity
    shakeIntensity_ -= shakeDecay_ * deltaTime;
    if (shakeIntensity_ < 0.0f) {
        shakeIntensity_ = 0.0f;
        shakeTimer_ = 0.0f;
    }
}

void Camera::clampToBounds() {
    if (!hasWorldBounds_) {
        return;
    }
    
    // Ensure camera doesn't show area outside world bounds
    float minCameraX = worldMinX_;
    float maxCameraX = worldMaxX_ - screenWidth_;
    float minCameraY = worldMinY_;
    float maxCameraY = worldMaxY_ - screenHeight_;
    
    // Handle case where world is smaller than screen
    if (maxCameraX < minCameraX) {
        x_ = (worldMinX_ + worldMaxX_ - screenWidth_) / 2.0f;
    } else {
        x_ = std::max(minCameraX, std::min(x_, maxCameraX));
    }
    
    if (maxCameraY < minCameraY) {
        y_ = (worldMinY_ + worldMaxY_ - screenHeight_) / 2.0f;
    } else {
        y_ = std::max(minCameraY, std::min(y_, maxCameraY));
    }
}

float Camera::worldToScreenX(float worldX) const {
    return worldX - x_ + shakeOffsetX_;
}

float Camera::worldToScreenY(float worldY) const {
    return worldY - y_ + shakeOffsetY_;
}

float Camera::screenToWorldX(float screenX) const {
    return screenX + x_ - shakeOffsetX_;
}

float Camera::screenToWorldY(float screenY) const {
    return screenY + y_ - shakeOffsetY_;
}

bool Camera::isPointVisible(float worldX, float worldY) const {
    return worldX >= x_ && worldX <= x_ + screenWidth_ &&
           worldY >= y_ && worldY <= y_ + screenHeight_;
}

bool Camera::isRectVisible(float worldX, float worldY, float width, float height) const {
    return !(worldX + width < x_ || worldX > x_ + screenWidth_ ||
             worldY + height < y_ || worldY > y_ + screenHeight_);
}

void Camera::shake(float intensity, float duration) {
    shakeIntensity_ = intensity;
    shakeTimer_ = duration;
}

float Camera::lerp(float start, float end, float t) const {
    return start + t * (end - start);
}

float Camera::random(float min, float max) const {
    static std::random_device rd;
    static std::mt19937 gen(rd());
    std::uniform_real_distribution<float> dis(min, max);
    return dis(gen);
}
